package org.iteam.mina.codec;

import java.nio.charset.Charset;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.iteam.mina.Constant;
import org.iteam.mina.message.BaseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 通用解码器
 * 
 * @author ke.wen
 * 
 */
public class MessageDecoder extends CumulativeProtocolDecoder {

	private static Logger log = LoggerFactory.getLogger(Constant.MINA_LOG_NAME);

	private Charset charset;

	public MessageDecoder(Charset charset) {
		this.charset = charset;
	}

	/**
	 * 解码
	 *
	 * @协议格式 总数据长度[4]命令[4]规则[4]消息协议版本[4]消息流水号[32]标识符[32]数据长度[4]数据内容
	 *       [根据数据长度定]签名数据长度[4]签名数据内容 [根据签名数据长度定]
	 */
	public void mydecode(IoSession session, IoBuffer buf, ProtocolDecoderOutput out) throws Exception {
		log.debug("decode消息长度:" + buf.remaining());
		if (session.isConnected() && buf.hasRemaining() && buf.prefixedDataAvailable(4)) {
			/***** 报头 *****/
			// 消息总长度(含消息头及消息体)
			int length = buf.getInt();
			// 消息命令或响应
			int cmd = buf.getInt();
			// 消息规则
			int rule = buf.getInt();
			// 消息版本
			int ver = buf.getInt();
			// 消息流水号,32位UUID字符串
			String seq = buf.getString(32, charset.newDecoder());
			/***** 报体 *****/
			// 令牌,32位UUID字符串
			String token = buf.getString(32, charset.newDecoder());
			// 获取校验方式
			int cmdFlag = (cmd & 0x0f000000) >>> 24;
			// 校验数据长度
			int verifyLength = 0;
			switch (cmdFlag) {
			// HMAC数据校验
			case 0x1:
			case 0x5:
			case 0x9:
			case 0xD:
				verifyLength = 16;
				break;
			// RSA签名校验
			case 0x2:
			case 0x6:
			case 0xA:
			case 0xE:
				verifyLength = 128;
				break;
			default:
				break;
			}
			byte[] content = null;
			byte[] verify = null;
			// 数据内容长度
			int contentLength = length - 4 * 3 - 32 * 2 - verifyLength;
			if (contentLength > 0) {
				content = new byte[contentLength];
				buf.get(content);
			}
			if (verifyLength > 0) {
				verify = new byte[verifyLength];
				buf.get(verify);
			}
			// 解码消息
			BaseMessage request = new BaseMessage();
			// 设置报头
			request.setLength(length);
			request.setCmd(cmd);
			request.setRule(rule);
			request.setVer(ver);
			request.setSeq(seq);
			// 设置报体
			request.setToken(token);
			request.setContent(content);
			request.setVerify(verify);
			out.write(request);
		} else {
			log.error("消息内容错误【长度错误】session.isConnected() && buf.hasRemaining() && buf.prefixedDataAvailable(4)");
		}
	}

	/**
	 * 
	 * 这个方法的返回值是重点：
	 * 
	 * 1、当内容刚好时，返回false，告知父类接收下一批内容
	 * 
	 * 2、内容不够时需要下一批发过来的内容，此时返回false，这样父类 CumulativeProtocolDecoder
	 * 
	 * 会将内容放进IoSession中，等下次来数据后就自动拼装再交给本类的doDecode
	 * 
	 * 3、当内容多时，返回true，因为需要再将本批数据进行读取，父类会将剩余的数据再次推送本
	 * 
	 * 类的doDecode
	 */
	@Override
	public boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		log.info("in.remaining : " + in.remaining());

		if (in.remaining() > 0) {// 有数据时，读取前8字节判断消息长度

			in.mark();// 标记当前位置，以便reset
			// 读取4字节
			int size = in.getInt();

			log.info("size : " + size);

			in.reset();

			if (size > in.remaining()) {// 如果消息内容不够，则重置，相当于不读取size
				return false;// 父类接收新数据，以拼凑成完整数据
			} else {
				mydecode(session, in, out);
				if (in.remaining() > 0) {// 如果读取内容后还粘了包，就让父类再重读 一次，进行下一次解析
					return true;
				}
			}
		}
		return false;// 处理成功，让父类进行接收下个包

	}
}
